import Quartz
from AppKit import NSEvent, NSAutoreleasePool
from PyQt4.QtCore import pyqtSignal, QThread
import sys
import logging

import config

logger = logging.getLogger(__name__)

try:
	from osdep.mac import sendpaste
except ImportError:
	sendpaste = None
	logger.warn("Could not load sendpaste c module")

class PlatformSpecificCode:
	def __init__(self, *args, **kwargs):
		if sendpaste:
			self._paste_text = sendpaste.send_paste
		else:
			self._paste_text = lambda: None	# do nothing
		super().__init__(*args, **kwargs)

	def register_hotkey(self):
		self._shortcut_handler = ShortcutHandler(self)
		self._shortcut_handler.start()
	
	def unregister_hotkey(self):
		self._shortcut_handler.stop_thread()
			
	def set_taskbar_text(self, text):
		# don't think the concept even exists for macs
		pass

class ShortcutHandler(QThread):
	_MODIFIER_TRANSFORMER = {config.MOD_ALT: Quartz.NSAlternateKeyMask, config.MOD_SHIFT: Quartz.NSShiftKeyMask,
							 config.MOD_CONTROL: Quartz.NSControlKeyMask, config.MOD_COMMAND: Quartz.NSCommandKeyMask }
							 
	_next_token_trigger = pyqtSignal()
	
	def __init__(self, parent):
		super().__init__()
		self._compute_modifiers()
		self._next_token_trigger.connect(parent.next_token)
		
	def _compute_modifiers(self):
		"""
		Computes the modifiers necessary to correctly decide whether a shortcut was pressed or not
		"""
		def compute_mask(modifiers):
			mask = 0
			for m in modifiers:
				mask |= m
			return mask

		modifiers, self._key = config.hotkey
		if self._key.islower():
			self._key = self._key.upper()
			logger.warn("hotkey character should be uppercase")
		used_mods = set()
		for m in modifiers:
			try:
				used_mods.add(self._MODIFIER_TRANSFORMER[m])
			except KeyError:
				logger.warn("Invalid modifier for windows - ignored.")
		if not used_mods:
			logger.warn("No shortcut modifier or only invalid ones specified. Use command as default.")
			used_mods = {Quartz.NSCommandKeyMask}
		unused_mods = set(self._MODIFIER_TRANSFORMER.values()) - used_mods
		self._used_modmask = compute_mask(used_mods)
		self._unused_modmask = compute_mask(unused_mods)
	
	def stop_thread(self):
		runloop = self._runloop
		if runloop:
			Quartz.CFRunLoopStop(runloop)
			
	def run(self):
		"""
		Uses quartz to get keyboard events. Has to be run in an extra thread because the looping constructs block until
		CFRunLoopStop is called.
		"""
		# set up an autorelease pool in thread
		NSAutoreleasePool.alloc().init()
		# Set up a tap, with type of tap, location, options and event mask
		tap = Quartz.CGEventTapCreate(
			Quartz.kCGSessionEventTap, # Session level is enough for our needs
			Quartz.kCGHeadInsertEventTap, # Insert wherever, we do not filter
			Quartz.kCGEventTapOptionListenOnly, # Listening is enough
			Quartz.CGEventMaskBit(Quartz.kCGEventKeyDown),
			self._keyboard_tap_callback, # callback
			None
		)
		if not tap:
			logger.error("Could not create keyboard tap. Enable assistive devices in "
						"System preferences->Universal access->enable access for assistive devices"
						" or run as root.")
			sys.exit(1)
		self._runloop = Quartz.CFRunLoopGetCurrent()
		runLoopSource = Quartz.CFMachPortCreateRunLoopSource(None, tap, 0)
		Quartz.CFRunLoopAddSource(
			Quartz.CFRunLoopGetCurrent(),
			runLoopSource,
			Quartz.kCFRunLoopDefaultMode
		)
		Quartz.CGEventTapEnable(tap, True)
		Quartz.CFRunLoopRun()
		# is run after unregister_hotkey has called CFRunLoopStop()
		Quartz.CFRelease(tap)
		# resources are automatically released, don't call pool.release() here!
		
	def _keyboard_tap_callback(self, proxy, type_, event, refCon):
		"""
		Our shortcut was pressed when key_event.key == self._key and
		modifierFlags include all declared in self._used_modmask and none in
		self._unused_modmask
		"""
		assert type_ == Quartz.kCGEventKeyDown
		key_event = NSEvent.eventWithCGEvent_(event)
		if key_event.charactersIgnoringModifiers().upper() != self._key: return event
		if key_event.isARepeat(): return event
		modifiers = key_event.modifierFlags()
		# all expected modifiers pressed?
		if modifiers & self._used_modmask != self._used_modmask: return event
		# some other modifier was also pressed
		if modifiers & self._unused_modmask != 0: return event
		# we call gui from background thread, hence we have to use signals
		self._next_token_trigger.emit()
		return event
